/**
 * Main.cpp
 *
 *
 */

#include <stdio.h>

#include "../Configuracion/Estructuras.h"
#include "../Controlador/Controlador.h"
#include "../Terminador/Terminador.h"
#include "../Configuracion/Utils.h"
#include "../Ascensor/Ascensor.h"
#include "../Piso/Piso.h"

using namespace std;

void informarParametrosInvalidos(enum ParametrosInvalidos error);
int validarParametros(int argc, char* argv[], Parametros* parametros);

void crearProcesosAscensores( Terminador* terminador, Parametros* parametros );
int crearAscensor( Parametros* parametros, int numeroAscensor );

void crearProcesoControlador( Terminador* terminador, Parametros* parametros );
int crearControlador(Terminador *terminador );

void crearProcesosPiso( Terminador* terminador, Parametros* parametros );
int crearPiso( Parametros* parametros, int nroPiso );

void ejecutarTerminador( Terminador* terminador );

int main(int argc, char *argv[]) {
	int retorno;

	// Se loguea siempre
	Log::getInstance()->setModoDebug( true );

	Parametros parametros;
	if ( ( retorno = validarParametros( argc, argv, &parametros ) ) == 0 ){
		Log::getInstance()->loguear( string ( argv[ 0 ] ) + " " + getStringParametros( &parametros ) );

		// Se crea el objeto para finalizar la ejecucicion de la aplicacion
		Terminador terminador( parametros.tiempo, parametros.cantidadPisos );

		// Se inicia la creacion de procesos
		crearProcesosAscensores( &terminador, &parametros );

	}
	Log::destruir();
	return retorno;
}

void informarParametrosInvalidos(enum ParametrosInvalidos error) {
	switch (error) {
		case CANTIDAD_INVALIDA: {
			Log::getInstance()->loguear("La cantidad de parametros es invalida.");
			break;
		}
		case CANTIDAD_PISOS_INVALIDA: {
			Log::getInstance()->loguear("La cantidad de pisos ingresados es invalida. Debe ser mayor a 0.");
			break;
		}
		case VELOCIDAD_ASCENSOR_INVALIDA: {
			Log::getInstance()->loguear("La velocidad del ascensor ingresada es invalida. Debe ser mayor a 0 y menor a 3 inclusive.");
			break;
		}
		case CAPACIDAD_ASCENSOR_INVALIDA: {
			Log::getInstance()->loguear("La capacidad máxima del ascensor ingresada es invalida. Debe ser mayor a 0.");
			break;
		}
		case TIEMPO_INVALIDO: {
			Log::getInstance()->loguear("La duracion de la simulación ingresada es invalida. Debe ser mayor a 0.");
			break;
		}
	}
}

int validarParametros(int argc, char* argv[], Parametros* parametros){
	int retorno = 0;
	if (argc != 5) {
		cerr << "Parametros invalidos" << endl;
		cerr << "Uso: " << argv[0] << " [Pisos] [Velocidad] [Capacidad] [Duración]" << endl;
		informarParametrosInvalidos(CANTIDAD_INVALIDA);
		retorno = -1;
	}
	else {
		int i = 1;

		int c = atoi(argv[i++]);
		if (c > 0) parametros->cantidadPisos = c;
		else {
			informarParametrosInvalidos(CANTIDAD_PISOS_INVALIDA);
			retorno = -1;
		}

		c = atoi(argv[i++]);
		if (c > 0 && c <= 3) parametros->velocidadAscensor = c;
		else {
			informarParametrosInvalidos(VELOCIDAD_ASCENSOR_INVALIDA);
			retorno = -1;
		}

		c = atoi(argv[i++]);
		if (c > 0) parametros->capacidadAscensor = c;
		else {
			informarParametrosInvalidos(CAPACIDAD_ASCENSOR_INVALIDA);
			retorno = -1;
		}

		c = atoi(argv[i++]);
		if (c > 0) parametros->tiempo = c;
		else {
			informarParametrosInvalidos(TIEMPO_INVALIDO);
			retorno = -1;
		}
	}
	return retorno;
}

void crearProcesosAscensores( Terminador* terminador, Parametros* parametros ) {

	pid_t pid;
	for ( int nroAscensor = 0; nroAscensor < CANTIDAD_DE_ASCENSORES; ++nroAscensor ){
		pid = fork();
		if ( pid == 0 ){
			if ( crearAscensor( parametros, nroAscensor) != 0 ) {
				kill( getppid(), SIGUSR1 );
			}
			break;
		} else {
			terminador->agregarPidAscensor( pid, nroAscensor );
			if ( nroAscensor == CANTIDAD_DE_ASCENSORES-1 ){
				crearProcesoControlador( terminador, parametros );
			}
		}
	}
}

int crearAscensor( Parametros* parametros, int numeroAscensor ) {
	Ascensor ascensor(numeroAscensor, parametros->velocidadAscensor, parametros->capacidadAscensor, parametros->cantidadPisos );
	int retorno = ascensor.inicializar();
	if ( retorno == 0 ) {
		ascensor.funcionar();
	}
	return retorno;
}

void crearProcesoControlador( Terminador* terminador, Parametros* parametros ){
	pid_t pid = fork();
	if ( pid == 0 ){
		if ( crearControlador(terminador ) != 0 ){
			kill( getppid(), SIGUSR1 );
		}
	}
	else {
		terminador->setPidControlador( pid );
		crearProcesosPiso( terminador, parametros );
	}
}

int crearControlador(Terminador *terminador ) {
	Controlador controlador = Controlador( );

	for (int nroAscensor = 0; nroAscensor < CANTIDAD_DE_ASCENSORES; ++nroAscensor) {
		controlador.agregarPidAscensor(terminador->getPidAscensor(nroAscensor),nroAscensor);
	}

	int retorno = controlador.inicializar();
	if ( retorno == 0 ) {
		controlador.recibirLlamadas();
	}
	return retorno;
}

void crearProcesosPiso( Terminador* terminador, Parametros* parametros ){
	pid_t pid;

	for ( int nroPiso = 0; nroPiso <= parametros->cantidadPisos; nroPiso++ ){
		pid = fork();
		if ( pid == 0 ){
			if ( crearPiso( parametros, nroPiso ) != 0 ) {
				kill( getppid(), SIGUSR1 );
			}
			break;
		} else {
			terminador->agregarPidPiso( pid );
			if ( nroPiso == parametros->cantidadPisos ){
				// Se ejecuta el terminador una vez que se terminaron de crear todos los procesos hijos.
				ejecutarTerminador( terminador );
			}
		}
	}
}

int crearPiso( Parametros* parametros, int nroPiso ){
	Piso piso( nroPiso, parametros->cantidadPisos );
	int retorno = piso.inicializar();
	if ( retorno == 0){
		piso.generarLlamadas();
	}
	return retorno;
}

void ejecutarTerminador( Terminador* terminador ){
	terminador->ejecutar();
}
